/*
 * Copyright 2010 Kevin L'Huillier <klhuillier@gmail.com>
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.lhuillier.pwsafe.io.codec;


import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Date;
import java.util.UUID;


class BinConverterImpl implements BinConverter {
    @Override
    public Date readTime32(byte[] data) {
        return readTime32(data, 0);
    }

    @Override
    public Date readTime32(byte[] data, int offset) {
        long time = 1000L * readInt(data, offset);
        return new Date(time);
    }

    @Override
    public short readShort(byte[] data) {
        return readShort(data, 0);
    }

    @Override
    public short readShort(byte[] data, int offset) {
        return (short) (byteToInt(data[offset])
                ^ (byteToInt(data[offset + 1]) << 8));
    }

    @Override
    public int readInt(byte[] data) {
        return readInt(data, 0);
    }

    @Override
    public int readInt(byte[] data, int offset) {
        return byteToInt(data[offset])
                ^ (byteToInt(data[offset + 1]) << 8)
                ^ (byteToInt(data[offset + 2]) << 16)
                ^ (byteToInt(data[offset + 3]) << 24);
    }

    @Override
    public String readString(byte[] data) {
        try {
            return new String(data, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new AssertionError("UTF-8 should be supported");
        }
    }

    @Override
    public String readString(byte[] data, int offset, int length) {
        return readString(slice(data, offset, length));
    }

    @Override
    public UUID readUuid(byte[] data) {
        return readUuid(data, 0);
    }

    @Override
    public UUID readUuid(byte[] data, int offset) {
        long msb = 0;
        long lsb = 0;
        long shift = 56;
        for (int i = offset; i < offset + 8; i++) {
            long hi = data[i];
            if (hi < 0) {
                hi += 256;
            }
            long lo = data[8 + i];
            if (lo < 0) {
                lo += 256;
            }
            msb += hi << shift;
            lsb += lo << shift;
            shift -= 8;
        }
        return new UUID(msb, lsb);
    }

    @Override
    public int[] hexToIntArray(byte[] data) {
        if ((data.length & 1) == 1) {
            throw new IllegalArgumentException(
                    "data must be an even number of bytes");
        }

        int[] result = new int[data.length / 2];

        for (int i = 0; i < result.length; i++) {
            result[i] = (Character.digit(data[i * 2], 16) << 4)
                    ^ Character.digit(data[i * 2 + 1], 16);
        }

        return result;
    }

    @Override
    public int[] hexToIntArray(byte[] data, int offset, int length) {
        return hexToIntArray(slice(data, offset, length));
    }

    @Override
    public byte[] slice(byte[] data, int offset, int length) {
        return Arrays.copyOfRange(data, offset, offset + length);
    }

    @Override
    public int byteToInt(byte b) {
        if (b < 0) {
            return b + 256;
        }
        return b;
    }
    
    @Override
    public byte[] writeString(String value) {
        try {
            return value.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new AssertionError("UTF-8 is not supported");
        }
    }

    @Override
    public byte[] writeByteToHex(byte value) {
        return writeString(String.format("%02x", value));
    }

    @Override
    public byte[] writeIntToHex(int value) {
        return writeString(String.format("%08x", value));
    }

    @Override
    public byte[] writeInt(int value) {
        return intToBytes(value);
    }

    @Override
    public byte[] writeShortToHex(short value) {
        return writeString(String.format("%04x", value));
    }

    @Override
    public byte[] writeShort(short value) {
        return shortToBytes(value);
    }

    @Override
    public byte[] writeTime32(Date value) {
        int secs = (int) (value.getTime() / 1000L);
        return intToBytes(secs);
    }

    @Override
    public byte[] writeUuid(UUID value) {
        byte[] result = new byte[16];
        long msb = value.getMostSignificantBits();
        long lsb = value.getLeastSignificantBits();
        for (int i = 7; i >= 0; i--) {
            result[i] = longToByte(msb);
            msb >>>= 8;
            result[8 + i] = longToByte(lsb);
            lsb >>>= 8;
        }
        return result;
    }

    /**
     * @param value any int
     * @return the lowest byte from the int
     */
    private byte intToByte(int value) {
        return (byte) (value & 0xff);
    }

    /**
     * @param value any long
     * @return the lowest byte from the long
     */
    private byte longToByte(long value) {
        return (byte) (value & 0xff);
    }

    /**
     * @param value any short
     * @return an array of two bytes representing the short
     */
    private byte[] shortToBytes(short value) {
        int promoted = value < 0 ? (0x8000 - value) : value;
        byte[] result = new byte[2];
        result[0] = intToByte(promoted);
        result[1] = intToByte(promoted >> 8);
        return result;
    }

    /**
     * @param value any int
     * @return an array of four bytes representing the int
     */
    private byte[] intToBytes(int value) {
        byte[] data = new byte[4];
        data[0] = intToByte(value);
        data[1] = intToByte(value >> 8);
        data[2] = intToByte(value >> 16);
        data[3] = intToByte(value >> 24);
        return data;
    }

}
